using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Xml.Linq;
using Microsoft.SharePoint;
using Sapphire.Linq.Framework;

namespace Sapphire.Linq.FieldMaps
{
    public abstract class PropertyMap
    {
        public abstract SPList List { get; }

        public abstract XElement GetFieldRef(Type objectType, string propertyName);
    }

    public class PropertyMap<T> : PropertyMap
    {
        private readonly ListQuery<T> _listQuery;
        private readonly Dictionary<string, FieldMap> _maps = new Dictionary<string, FieldMap>();

        public PropertyMap(ListQuery<T> listQuery)
        {
            _listQuery = listQuery;
        }

        public PropertyMap<T> SetAllAuto()
        {
            TypeDescriptor.GetProperties(typeof(T))
                .Cast<PropertyDescriptor>()
                .ToList()
                .ForEach(p => _maps[p.Name] = new FieldMap(p.Name));

            return this;
        }

        public PropertyMap<T> But(Expression<Func<T, object>> map, Guid id)
        {
            RegisterFieldMap(map, id);
            return this;
        }

        public PropertyMap<T> But(Expression<Func<T, object>> map, string name)
        {
            RegisterFieldMap(map, name);
            return this;
        }

        public PropertyMap<T> Register(Expression<Func<T, object>> map, Guid id)
        {
            RegisterFieldMap(map, id);
            return this;
        }

        public PropertyMap<T> Register(Expression<Func<T, object>> map, string name)
        {
            RegisterFieldMap(map, name);
            return this;
        }

        private void RegisterFieldMap(Expression<Func<T, object>> map, string value)
        {
            _maps[GetName(map)] = new FieldMap(value);
        }

        private void RegisterFieldMap(Expression<Func<T, object>> map, Guid value)
        {
            _maps[GetName(map)] = new FieldMap(value);
        }

        private string GetName(Expression<Func<T, object>> map)
        {
            var memberExpression = map.Body as MemberExpression;

            if (memberExpression == null)
            {
                var unaryExpression = map.Body as UnaryExpression;
                if (unaryExpression != null)
                    memberExpression = unaryExpression.Operand as MemberExpression;
            }

            return memberExpression.Member.Name;
        }

        public override SPList List
        {
            get { return _listQuery.List; }
        }

        public override XElement GetFieldRef(Type objectType, string propertyName)
        {
            if (objectType != typeof(T))
                throw new ArgumentException("Incorrect type", "objectType");

            var fieldMap = _maps[propertyName];
            return fieldMap.GetFieldRef();
        }

        public SPField FindField(SPList list, string propertyName)
        {
            var fieldMap = _maps[propertyName];
            return fieldMap.FindField(list, propertyName);
        }
    }
}